﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vds2465Telegrams;

namespace Vds2465Parser
{
	internal static class GenParse_UserData_30_Measurements
	{
		#region private constants

		// all bytes including the code byte (Werteart) but not the value bytes
		private const int NrOfHeaderBytes = 7;

		private const int ValueBytes_RepType01 = 2;
		private const int ValueBytes_RepType02 = 2;
		private const int ValueBytes_RepType03 = 3;

		private const byte CodeByte_RepType02 = 0x80;
		private const byte CodeByte_RepType03 = 0x88;

		#endregion private constants

		#region internal methods

		internal static byte[] GenerateBytes(UserData userData)
		{
			byte[] userDataBytes = null;

			if (userData is UserData_30_Measurements)
			{
				UserData_30_Measurements userData30 = userData as UserData_30_Measurements;
				byte[] valueBytes;
				byte codeByte = 0x00; ;
				switch (userData30.RepresentationType)
				{
					case eRepresentationType.RepType_01:
						GenerateBytes_RepType01(userData30.MeasurementType, userData30.MeasurementValue, out codeByte, out valueBytes);
						break;
					case eRepresentationType.RepType_02:
						GenerateBytes_RepType02(userData30.LowByte, userData30.HighByte, out codeByte, out valueBytes);
						break;
					case eRepresentationType.RepType_03:
						GenerateBytes_RepType03(userData30.Byte01, userData30.Byte02, userData30.Byte03, out codeByte, out valueBytes);
						break;
					case eRepresentationType.RepType_Free:
						GenerateBytes_RepTypeFree(userData30.FreeCodeByte, userData30.GetCopyOfFreeBytes(), out codeByte, out valueBytes);
						break;
					default:
						// TODO handle error
						return null;
						// break;
				}

				// calculate number of bytes
				int totalNrOfBytes = NrOfHeaderBytes +  valueBytes.Length;
				if (totalNrOfBytes > 255)
				{
					// TODO handle error
					// TODO is 255 the correct number or do we need a smaller number
				}

				userDataBytes = new byte[totalNrOfBytes];
				userDataBytes[0] = (byte)(totalNrOfBytes - GenParse_UserData.HeaderLength);	// user data length
				userDataBytes[1] = UserData.UserData30Measurements;
				userDataBytes[2] = userData30.Device;
				userDataBytes[3] = userData30.Address;
				userDataBytes[4] = userData30.AddressAddOn;
				userDataBytes[5] = GenParse_AddressExtension.GenerateByte_AddExt_1(userData30.AddressExtension);
				userDataBytes[6] = codeByte;
				for (int index = 0; index < valueBytes.Length; index++)
				{
					userDataBytes[index + 7] = valueBytes[index];
				}
			}
			else
			{
				// TODO handle error
			}

			return userDataBytes;
		}

		internal static bool ParseBytes(byte[] userDataBytes, out UserData userData30Measurements, out int consumedBytes)
		{
			userData30Measurements = null;
			consumedBytes = 0;

			// we need at least one more byte than the header bytes
			if (userDataBytes.Length > NrOfHeaderBytes)
			{
				byte setLength = userDataBytes[0];
				if (userDataBytes.Length >= setLength + 2)
				{
					if (userDataBytes[1] == UserData.UserData30Measurements)
					{
						byte device = userDataBytes[2];
						byte address = userDataBytes[3];
						byte addressAddOn = userDataBytes[4];

						eAddressExtension_1 addressExtension;
						bool bRetVal = GenParse_AddressExtension.ParseByte_AddExt_1(userDataBytes[5], out addressExtension);
						if (bRetVal)
						{
							int nrOfBlockStateBytes = userDataBytes.Length - NrOfHeaderBytes;
							eRepresentationType representationType;
							eMeasurementType measurementType;
							bRetVal = ParseCodeByte(userDataBytes[6], out representationType, out measurementType);
							if (bRetVal)
							{
								int nrOfMeasurementValueBytes = userDataBytes.Length - NrOfHeaderBytes;
								byte[] measurementValueBytes = new byte[nrOfMeasurementValueBytes];
								for (int index = 0; index < nrOfMeasurementValueBytes; index++)
								{
									measurementValueBytes[index] = userDataBytes[7 + index];
								}
								switch (representationType)
								{
									case eRepresentationType.RepType_01:
										return ParseBytes_RepType01(device, address, addressAddOn, addressExtension,
														measurementType, measurementValueBytes, 
														out userData30Measurements, out consumedBytes);
									case eRepresentationType.RepType_02:
										return ParseBytes_RepType02(device, address, addressAddOn, addressExtension,
														measurementValueBytes,
														out userData30Measurements, out consumedBytes);
									case eRepresentationType.RepType_03:
										return ParseBytes_RepType03(device, address, addressAddOn, addressExtension,
														measurementValueBytes,
														out userData30Measurements, out consumedBytes);
									case eRepresentationType.RepType_Free:
										return ParseBytes_RepTypeFree(device, address, addressAddOn, addressExtension,
														userDataBytes[6], measurementValueBytes,
														out userData30Measurements, out consumedBytes);
									default:
										// TODO handle error
										return false;
								}							
							}

							return false;
						}
						else
						{
							// TODO handle error: ParseByte_AddExt_1 has failed
							return false;
						}
					}

					// TODO handle error: wrong value for the data type
					return false;
				}

				// TODO handle error: not enough bytes as specified by the SL value
				return false;
			}

			// TODO handle error (not enough bytes)
			return false;
		}

		#endregion internal methods

		#region private methods

		#region GenerateBytes

		private static void GenerateBytes_RepType01(eMeasurementType measurementType, double measurementValue, out byte codeByte, out byte[] valueBytes)
		{
			codeByte = GenParse_MeasurementType.GenerateByte(measurementType);
			valueBytes = new byte[ValueBytes_RepType01];

			GenParse_UserData_30_RepType01Numbers.Generate_RepType01_Bytes(measurementValue, out valueBytes[0], out valueBytes[1]);
		}

		private static void GenerateBytes_RepType02(byte lowByte, byte highByte, out byte codeByte, out byte[] valueBytes)
		{
			codeByte = CodeByte_RepType02;
			valueBytes = new byte[ValueBytes_RepType02];
			valueBytes[0] = lowByte;
			valueBytes[1] = highByte;
		}

		private static void GenerateBytes_RepType03(byte byte01, byte byte02, byte byte03, out byte codeByte, out byte[] valueBytes)
		{
			codeByte = CodeByte_RepType03;
			valueBytes = new byte[ValueBytes_RepType03];
			valueBytes[0] = byte01;
			valueBytes[1] = byte02;
			valueBytes[2] = byte03;
		}

		private static void GenerateBytes_RepTypeFree(byte freeCodeByte, byte[] freeBytes, out byte codeByte, out byte[] valueBytes)
		{
			codeByte = freeCodeByte;
			if (freeBytes != null)
			{
				valueBytes = new byte[freeBytes.Length];
				for (int index = 0; index < freeBytes.Length; index++)
				{
					valueBytes[index] = freeBytes[index];
				}
			}
			else
			{
				valueBytes = new byte[0];
			}
		}

		#endregion GenerateBytes

		#region ParseBytes

		/// <summary>
		/// Parse the code byte and determine eRepresentationType and possibly eMeasurementType
		/// </summary>
		/// <param name="codeByte">the code byte to parse</param>
		/// <param name="representationType">the resulting representationType</param>
		/// <param name="measurementType">the resulting MeasurementType, only meaningful if the representationType is RepType01</param>
		/// <returns>true: parsing was successful, false otherwise</returns>
		private static bool ParseCodeByte(byte codeByte, out eRepresentationType representationType, out eMeasurementType measurementType)
		{
			// dummy default value
			measurementType = eMeasurementType.RadioactiveEnergyDose;
			representationType = eRepresentationType.RepType_Free;

			if (GenParse_MeasurementType.ParseByte(codeByte, out measurementType))
			{
				representationType = eRepresentationType.RepType_01;
				return true;
			}
			if (codeByte == CodeByte_RepType02)
			{
				representationType = eRepresentationType.RepType_02;
				return true;
			}
			if (codeByte == CodeByte_RepType03)
			{
				representationType = eRepresentationType.RepType_03;
				return true;
			}
			if (codeByte >= UserData_30_Measurements.MinFreeCode)
			{
				representationType = eRepresentationType.RepType_Free;
				return true;
			}

			// TODO handle error
			return false;
		}

		private static bool ParseBytes_RepType01(byte device, byte address, byte addressAddOn, eAddressExtension_1 addressExtension,
														eMeasurementType measurementType, byte[] measurementValueBytes,
														out UserData userData30Measurements, out int consumedBytes)
		{
			userData30Measurements = null;
			consumedBytes = -1;

			if (measurementValueBytes.Length >= ValueBytes_RepType01)
			{
				double measurementValue = GenParse_UserData_30_RepType01Numbers.Parse_RepType01_Bytes(measurementValueBytes[0], measurementValueBytes[1]);
				userData30Measurements = new UserData_30_Measurements(device, address, addressAddOn, addressExtension, measurementType, measurementValue);
				consumedBytes = NrOfHeaderBytes + ValueBytes_RepType01;
				return true;
			}

			// TODO handle error - not enough bytes
			return false;
		}

		private static bool ParseBytes_RepType02(byte device, byte address, byte addressAddOn, eAddressExtension_1 addressExtension,
														byte[] measurementValueBytes,
														out UserData userData30Measurements, out int consumedBytes)
		{
			userData30Measurements = null;
			consumedBytes = -1;

			if (measurementValueBytes.Length >= ValueBytes_RepType02)
			{
				userData30Measurements = new UserData_30_Measurements(device, address, addressAddOn, addressExtension, measurementValueBytes[0], measurementValueBytes[1]);
				consumedBytes = NrOfHeaderBytes + ValueBytes_RepType02;
				return true;
			}

			// TODO handle error - not enough bytes
			return false;
		}

		private static bool ParseBytes_RepType03(byte device, byte address, byte addressAddOn, eAddressExtension_1 addressExtension,
														byte[] measurementValueBytes,
														out UserData userData30Measurements, out int consumedBytes)
		{
			userData30Measurements = null;
			consumedBytes = -1;

			if (measurementValueBytes.Length >= ValueBytes_RepType03)
			{
				userData30Measurements = new UserData_30_Measurements(device, address, addressAddOn, addressExtension, measurementValueBytes[0], measurementValueBytes[1], measurementValueBytes[2]);
				consumedBytes = NrOfHeaderBytes + ValueBytes_RepType03;
				return true;
			}

			// TODO handle error - not enough bytes
			return false;
		}

		private static bool ParseBytes_RepTypeFree(byte device, byte address, byte addressAddOn, eAddressExtension_1 addressExtension,
														byte freeCodeByte, byte[] measurementValueBytes,
														out UserData userData30Measurements, out int consumedBytes)
		{
			userData30Measurements = null;
			consumedBytes = -1;

			userData30Measurements = new UserData_30_Measurements(device, address, addressAddOn, addressExtension, freeCodeByte, measurementValueBytes);
			consumedBytes = NrOfHeaderBytes;
			if (measurementValueBytes != null)
			{
				consumedBytes += measurementValueBytes.Length;
			}

			return true;
		}
				
		#endregion ParseBytes

		#endregion private methods
	}
}
